﻿using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Text;

namespace Common.OjbectPool
{
    public class DefaultPooledObject<T> : IPooledObject<T>
    {
        private readonly  Object objLock=new object();
        private readonly T realObj;
        private   PooledObjectState state = PooledObjectState.IDLE; // @GuardedBy("this") to ensure transitions are valid
        private readonly long createTime;
        private long lastBorrowTime;//atomic long;
        private long lastUseTime;//atomic long;
        private long lastReturnTime;//atomic long;
   
        

        private DefaultPooledObject(T obj)
        {
            this.realObj = obj;
            createTime = GetCurrentTimeMillis();
            lastBorrowTime = createTime;
            lastUseTime = createTime;
            lastReturnTime = createTime;

        }

        public T GetObject()
        {
            return realObj;
        }

        public long GetCreateTime()
        {
            return createTime;
        }

        public long GetActiveTimeMillis()
        {
            // Take copies to avoid threading issues
            var rTime = lastReturnTime;
            var bTime = lastBorrowTime;

            if (rTime > bTime)
            {
                return rTime - bTime;
            }
            return GetCurrentTimeMillis() - bTime;
        }

        public long GetIdleTimeMillis()
        {
            return GetCurrentTimeMillis() - lastReturnTime;
        }

        public long GetLastBorrowTime()
        {
            return lastBorrowTime;
        }

        public long GetLastReturnTime()
        {
            return lastReturnTime;
        }

        public long GetLastUsedTime()
        {
            //   if (object instanceof TrackedUse) {
            //    return Math.max(((TrackedUse) object).getLastUsed(), lastUseTime);
            //} else 
            {
                return lastUseTime;
            }
        }

        public bool StartEvictionTest()
        {
            if (state == PooledObjectState.IDLE)
            {
                state = PooledObjectState.EVICTION;
                return true;
            }

            return false;
        }

        public bool EndEvictionTest(ConcurrentQueue<IPooledObject<T>> idleQueue)
        {
            if (state == PooledObjectState.EVICTION)
            {
                state = PooledObjectState.IDLE;
                return true;
            }
            if (state == PooledObjectState.EVICTION_RETURN_TO_HEAD)
            {
                state = PooledObjectState.IDLE;
                idleQueue.Enqueue(this);

            }

            return false;
        }

        public bool Allocate()
        {
            lock (objLock)
            {
                if (state == PooledObjectState.IDLE)
                {
                    state = PooledObjectState.ALLOCATED;
                    Interlocked.Exchange(ref lastBorrowTime, GetCurrentTimeMillis());
                    Interlocked.Exchange(ref lastUseTime, lastBorrowTime);
 
                    return true;
                }
                if (state == PooledObjectState.EVICTION)
                {
                    // TODO Allocate anyway and ignore eviction test
                    state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
                    return false;
                }
                // TODO if validating and testOnBorrow == true then pre-allocate for
                // performance
                return false;
            }
        }

        public bool Deallocate()
        {
            lock (objLock)
            {
                if (state == PooledObjectState.ALLOCATED ||
                    state == PooledObjectState.RETURNING)
                {
                    state = PooledObjectState.IDLE;
                    Interlocked.Exchange(ref lastReturnTime, GetCurrentTimeMillis());
                    return true;
                }

                return false;
            }
        }

        public void Invalidate()
        {
            lock (objLock)
            {
                state = PooledObjectState.INVALID;
            }
        }

        

        public void Use()
        {
           Interlocked.Exchange(ref lastUseTime,GetCurrentTimeMillis());
            
        }

        public  PooledObjectState GetState()
        {
            lock (objLock)
            {
                  return state;
            }
        }

        public void MarkAbandoned()
        {
            lock (objLock)
            {
                state = PooledObjectState.ABANDONED;
            }
        }

        public void MarkReturning()
        {
            lock (objLock)
            {
                state = PooledObjectState.RETURNING;
            }
        }

        public int CompareTo(IPooledObject<T> other)
        {
            long lastActiveDiff = this.GetLastReturnTime() - other.GetLastReturnTime();
            if (lastActiveDiff == 0)
            {
                //java use
                //return System.identityHashCode(this) - System.identityHashCode(other);
                //identity hascode in appdomain,not override hashcode
                //TODO: use this==other?
                return RuntimeHelpers.GetHashCode(this) - RuntimeHelpers.GetHashCode(other);
            }
            // handle int overflow
            return (int)Math.Min(Math.Max(lastActiveDiff, Int32.MinValue), Int32.MaxValue);
        }

        

        public override string ToString()
        {
            var result = new StringBuilder();
            result.Append("Object: ");
            result.Append(realObj.ToString());
            result.Append(", State: ");

            lock (this)
            {
                result.Append(state.ToString());
            }
            return result.ToString();
        }
        private long GetCurrentTimeMillis()
        {
            return (long)DateTime.Now.Subtract(DateTime.Parse("1970-1-1")).TotalMilliseconds;
        }
    }
}
